package cn.huaqingcheng.tool.http;

import cn.huaqingcheng.tool.core.CollTool;
import cn.huaqingcheng.tool.core.StrTool;
import cn.huaqingcheng.tool.core.function.Function;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;

/**
 * 包装请求
 * TODO: 序列化相关暂未实现, 有两种方案 URL编码 / Base64
 *
 * @apiNote {@link #getAll()} 可以获取所有存储映射的不可修改副本
 * @implNote 实现是非线程安全的; 应该不可能有多线程共同构建一个请求参数的必要...
 * @see HttpHeader
 * @see HttpRequest
 * @see HttpResponse
 */
@NoArgsConstructor
@JsonAutoDetect(
    getterVisibility = JsonAutoDetect.Visibility.NONE,
    isGetterVisibility = JsonAutoDetect.Visibility.NONE,
    fieldVisibility = JsonAutoDetect.Visibility.NONE
)
@SuppressWarnings({"ClassNamePrefixedWithPackageName", "unused"})
public class HttpHeaders {

    /**
     * 存储, 所有的 key 必须为小写
     */
    @NotNull
    private final Map<String, String> value = new HashMap<>(16, 1);

    public HttpHeaders(java.net.http.HttpHeaders headers) {
        headers.map().forEach((k, vl) -> value.put(k, String.join(StrTool.SPACE, vl)));
    }

    /**
     * @see #add(String, String)
     */
    public HttpHeaders add(@Nullable HttpHeader name, @Nullable String value) {
        if (name != null) {
            return add(name.getValue(), value);
        }
        return this;
    }

    /**
     * 添加, 如果已存在, 则覆盖
     *
     * @param name  如果为空, 忽略; 不区分大小写
     * @param value 允许为空
     */
    @Contract("_, _ -> this")
    public HttpHeaders add(@Nullable String name, @Nullable String value) {
        if (StrTool.isNotEmpty(name)) {
            this.value.put(name.toLowerCase(), value);
        }
        return this;
    }

    /**
     * @see #addIfNotExist(String, String)
     */
    public HttpHeaders addIfNotExist(@Nullable HttpHeader name, @Nullable String value) {
        if (name != null) {
            return addIfNotExist(name.getValue(), value);
        }
        return this;
    }

    /**
     * 添加, 如果不存在
     *
     * @param name  如果为空, 忽略; 不区分大小写
     * @param value 允许为空
     */
    @Contract("_, _ -> this")
    public HttpHeaders addIfNotExist(@Nullable String name, @Nullable String value) {
        if (StrTool.isNotEmpty(name)) {
            this.value.putIfAbsent(name.toLowerCase(), value);
        }
        return this;
    }

    /**
     * @see #addIfNotExist(String, Function)
     */
    public HttpHeaders addIfNotExist(@Nullable HttpHeader name, @NotNull Function<String, String, Exception> fun) {
        if (name != null) {
            return addIfNotExist(name.getValue(), fun);
        }
        return this;
    }

    /**
     * 添加, 如果不存在
     *
     * @param name 头名称, 不区分大小写
     * @param fun  如果不存在, 执行的函数, 如果返回值为空则忽略
     */
    @Contract("_, _ -> this")
    public HttpHeaders addIfNotExist(@Nullable String name, @NotNull Function<String, String, Exception> fun) {
        if (StrTool.isNotEmpty(name)) {
            String key = name.toLowerCase();
            value.computeIfAbsent(
                key, k -> {
                    try {
                        return fun.apply(key);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            );
        }
        return this;
    }

    /**
     * 添加多个
     *
     * @see #add(String, String)
     */
    @SuppressWarnings("UnusedReturnValue")
    public HttpHeaders addAll(Map<String, String> all) {
        if (CollTool.isNotEmpty(all)) {
            all.forEach(this::add);
        }
        return this;
    }

    /**
     * @see #get(String)
     */
    @Contract(pure = true)
    public Optional<String> getOpt(@Nullable String name) {
        return Optional.ofNullable(get(name));
    }

    /**
     * 获取指定的请求头
     *
     * @param name 头名称, 允许为空(返回 null); 不区分大小写
     * @return 可能为空的请求头值
     */
    @Nullable
    @Contract(value = "null -> null", pure = true)
    public String get(@Nullable String name) {
        if (StrTool.isNotEmpty(name)) {
            return value.get(name.toLowerCase());
        }
        return null;
    }

    /**
     * @see #get(HttpHeader)
     */
    @Contract(pure = true)
    public Optional<String> getOpt(@Nullable HttpHeader name) {
        return Optional.ofNullable(get(name));
    }

    /**
     * @see #get(String)
     */
    @Nullable
    @Contract(pure = true)
    public String get(@Nullable HttpHeader name) {
        if (name != null) {
            return get(name.getValue());
        }
        return null;
    }

    /**
     * @see #getOrDefault(String, String)
     */
    @Contract(value = "null, _ -> param2; !null, _ -> new", pure = true)
    public String getOrDefault(@Nullable HttpHeader name, @NotNull String def) {
        if (name != null) {
            return getOrDefault(name.getValue(), def);
        }
        return Objects.requireNonNull(def);
    }

    /**
     * 获取指定的请求头
     *
     * @param name 头名称, 允许为空(返回 null); 不区分大小写
     * @param def  如果指定的请求头不存在,或者映射到 null, 使用此默认值
     */
    @Contract(value = "null, _ -> param2; !null, _ -> new", pure = true)
    public String getOrDefault(@Nullable String name, @NotNull String def) {
        if (StrTool.isNotEmpty(name)) {
            return value.getOrDefault(def, Objects.requireNonNull(def));
        }
        return Objects.requireNonNull(def);
    }

    public boolean isEmpty() {
        return value.isEmpty();
    }

    public boolean isNotEmpty() {
        return !value.isEmpty();
    }

    /**
     * 返回所有的值
     *
     * @return 只读的映射
     */
    @Contract(pure = true)
    @JsonAnyGetter
    public Map<String, String> getAll() {
        return Collections.unmodifiableMap(value);
    }

    /**
     * 遍历所有属性
     */
    public void forEach(BiConsumer<? super String, ? super String> consumer) {
        value.forEach(consumer);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(value.size() * 5);
        value.forEach((k, v) -> sb.append(k).append(": ").append(v).append("\n"));
        sb.deleteCharAt(sb.length() - 1);
        sb.trimToSize();

        return sb.toString();
    }

}
